package com.tgy.MyFlowable.workflow.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ctsi.core.common.enums.StatusType;
import com.ctsi.core.common.exception.MarsException;
import com.ctsi.core.common.util.$;
import com.ctsi.core.common.util.SecurityUtil;
import com.ctsi.core.common.util.StringPool;
import com.ctsi.core.database.util.PageUtil;
import com.ctsi.workflow.dto.FlwDefinitionDTO;
import com.ctsi.workflow.dto.FlwUserTaskVO;
import com.ctsi.workflow.enums.FlwDefinitionExceptionEnum;
import com.ctsi.workflow.service.*;
import com.ctsi.workflow.vo.FlwDefinitionVO;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.DeploymentBuilder;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.flowable.ui.common.service.exception.NotFoundException;
import org.flowable.ui.modeler.domain.AbstractModel;
import org.flowable.ui.modeler.domain.Model;
import org.flowable.ui.modeler.model.ModelRepresentation;
import org.flowable.ui.modeler.service.BpmnDisplayJsonConverter;
import org.flowable.ui.modeler.serviceapi.ModelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * 流程定义业务接口实现类
 *
 * @version 1.0
 * @author: wang xiao xiang
 * @date: 2021/8/8 19:25
 */
@Slf4j
@Service
public class FlwDefinitionServiceImpl implements IFlwDefinitionService {

    /**
     * 模型资源后缀
     */
    private static final String MODEL_BPMN_SUFFIX = ".bpmn20.xml";

    /**
     * 启动节点默认启动人标志
     */
    private static final String INITIATOR = "INITIATOR";

    /**
     * 用户任务节点流程发起人默认标志
     */
    private static final String SYMBOL_INITIATOR = "$INITIATOR";

    /**
     * 部署时需替换的用户任务节点流程发起人变量标志
     */
    private static final String DOUBLE_SYMBOL_INITIATOR = "${INITIATOR}";

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private ModelService modelService;

    @Resource
    private IFlwOptionService flwOptionService;

    @Autowired
    private IFlwCategoryService flwCategoryService;

    @Autowired
    private IFlwShortcutService flwShortcutService;

    @Autowired
    private IFlwFormService flwFormService;

    @Autowired
    private IFlwEventService flwEventService;

    @Autowired
    private IFlwButtonService flwButtonService;

    @Autowired
    private HttpServletRequest request;

    @Override
    public Page<FlwDefinitionVO> page(FlwDefinitionDTO flwDefinitionDTO) {
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
        if (ObjectUtil.isNotNull(flwDefinitionDTO)) {
            //根据key模糊查询
            if (ObjectUtil.isNotEmpty(flwDefinitionDTO.getKey())) {
                processDefinitionQuery.processDefinitionKeyLike(StringPool.PERCENT + flwDefinitionDTO.getKey() + StringPool.PERCENT);
            }
            //根据名称模糊查询
            if (ObjectUtil.isNotEmpty(flwDefinitionDTO.getName())) {
                processDefinitionQuery.processDefinitionNameLike(StringPool.PERCENT + flwDefinitionDTO.getName() + StringPool.PERCENT);
            }
            //根据分类查询
            if (ObjectUtil.isNotEmpty(flwDefinitionDTO.getCategory())) {
                processDefinitionQuery.processDefinitionCategory(flwDefinitionDTO.getCategory());
            }
            //根据挂起状态查询
            if (flwDefinitionDTO.getSuspended()) {
                processDefinitionQuery.suspended();
            }
            //默认查询最新版本
            if (flwDefinitionDTO.getLastedVersion()) {
                processDefinitionQuery.latestVersion();
            }
        }

        Page<FlwDefinitionVO> defaultPage = PageUtil.getPage(flwDefinitionDTO);
        long current = defaultPage.getCurrent();
        long size = defaultPage.getSize();
        List<ProcessDefinition> processDefinitionList = processDefinitionQuery
                .listPage(Convert.toInt((current - 1) * size), Convert.toInt(size));
        List<FlwDefinitionVO> flwDefinitionVOList = processDefinitionList.stream()
                .map(processDefinition -> {
                    // 拷贝对象
                    FlwDefinitionVO flwDefinitionVO = $.copy(processDefinition, FlwDefinitionVO.class);
                    // 根据code从分类中查询名称
                    flwDefinitionVO.setCategoryName(flwCategoryService.getNameByCode(processDefinition.getCategory()));
                    // 设置命名不一致的字段，是否挂起
                    flwDefinitionVO.setSuspended(processDefinition.isSuspended());
                    return flwDefinitionVO;
                })
                .collect(Collectors.toList());
        defaultPage.setTotal(processDefinitionQuery.count());
        defaultPage.setRecords(flwDefinitionVOList);
        return defaultPage;
    }

    @Override
    public void deploy(FlwDefinitionDTO flwDefinitionDTO) {
        //此model为设计器model，非act_re_model
        String modelId = flwDefinitionDTO.getModelId();

        //流程分类
        String category = flwDefinitionDTO.getCategory();

        //流程模型
        Model designModel;

        try {
            //获取流程模型
            designModel = modelService.getModel(modelId);
        } catch (NotFoundException e) {
            //捕获异常该流程模型不存在
            throw new MarsException(FlwDefinitionExceptionEnum.MODEL_NOT_EXIST);
        }

        //获取bpmnModel
        BpmnModel bpmnModel = modelService.getBpmnModel(designModel);

        //下面这段代码是用于修复流程发起人参数，关于这个问题详见https://github.com/flowable/flowable-engine/issues/534
        //关于initiator作用以及使用见http://www.shareniu.com/article/191.htm
        bpmnModel.getMainProcess().getFlowElements().forEach(flowElement -> {
            //遍历节点，若为开始事件，则设置INITIATOR
            if (flowElement instanceof StartEvent) {
                StartEvent startEvent = (StartEvent) flowElement;
                startEvent.setInitiator(INITIATOR);
            }
            //遍历节点，若任务节点的待办人为$INITIATOR（流程引擎默认的标识，不能用于设置流程启动人参数，需替换为${INITIATOR}）
            if (flowElement instanceof UserTask) {
                UserTask userTask = (UserTask) flowElement;
                String assignee = userTask.getAssignee();
                if (ObjectUtil.isNotEmpty(assignee)) {
                    //若任务节点的待办人为$INITIATOR则替换为${INITIATOR}
                    if (SYMBOL_INITIATOR.equals(assignee)) {
                        userTask.setAssignee(DOUBLE_SYMBOL_INITIATOR);
                    }
                }
            }
        });

        if ($.isEmpty(bpmnModel)) {
            //模型资源为空，则抛异常该流程模型无资源
            throw new MarsException(FlwDefinitionExceptionEnum.MODEL_HAS_NO_RESOURCE);
        }
        //定义一个部署构造器
        DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
        //流程命名与模型相同
        deploymentBuilder.name(designModel.getName());
        //构造部署的流程资源名称
        String deploymentName = designModel.getName() + MODEL_BPMN_SUFFIX;
        //设置流程资源
        deploymentBuilder.addBpmnModel(deploymentName, bpmnModel);
        //设置流程分类
        deploymentBuilder.category(category);
        //部署
        try {
            Deployment deployment = deploymentBuilder.deploy();
            String deploymentId = deployment.getId();
            //部署后根据deploymentId查询到processDefinition，设置分类
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                    .deploymentId(deploymentId).singleResult();
            repositoryService.setProcessDefinitionCategory(processDefinition.getId(), category);

            //部署后自动插入一条流程选项设置
            flwOptionService.addByProcessDefinition(processDefinition);
        } catch (Exception e) {
            log.error(">>> 流程部署失败，具体信息为：{}", e.getMessage());
            throw new MarsException(FlwDefinitionExceptionEnum.DEPLOYMENT_ERROR);
        }
    }

    @Override
    public JsonNode trace(FlwDefinitionDTO flwDefinitionDTO) {

        //获取流程定义
        ProcessDefinition processDefinition = this.queryProcessDefinition(flwDefinitionDTO.getId());

        //根据流程定义获取deployment
        String deploymentId = processDefinition.getDeploymentId();
        Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult();
        if (ObjectUtil.isEmpty(deployment)) {
            throw new MarsException(FlwDefinitionExceptionEnum.DEPLOYMENT_NOT_EXIST);
        }

        //获取bpmnModel并转为modelNode
        BpmnModel bpmnModel = repositoryService.getBpmnModel(flwDefinitionDTO.getId());
        ObjectNode modelNode = new BpmnJsonConverter().convertToJson(bpmnModel);

        //构建model并设置流程设计资源
        Model model = new Model();
        model.setModelEditorJson(modelNode.toString());

        //获取流程图显示节点数据
        ObjectNode displayNode = new ObjectMapper().createObjectNode();
        new BpmnDisplayJsonConverter().processProcessElements(model, displayNode, new GraphicInfo());
        return displayNode;
    }

    @Override
    public ProcessDefinition queryProcessDefinition(String id) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(id).singleResult();
        boolean suspended = processDefinition.isSuspended();
        if (suspended) {
            throw new MarsException(FlwDefinitionExceptionEnum.DEFINITION_SUSPEND);
        }
        return processDefinition;
    }

    @Override
    public FlwDefinitionVO detail(String id) {
        ProcessDefinition processDefinition = this.queryProcessDefinition(id);
        FlwDefinitionVO flwDefinitionVO = $.copy(processDefinition, FlwDefinitionVO.class);
        flwDefinitionVO.setCategoryName(flwCategoryService.getNameByCode(processDefinition.getCategory()));
        flwDefinitionVO.setSuspended(processDefinition.isSuspended());
        return flwDefinitionVO;
    }

    @Override
    public void export(FlwDefinitionDTO flwDefinitionDTO, HttpServletResponse response) {
        //获取流程定义
        ProcessDefinition processDefinition = getProcessDefinition(flwDefinitionDTO);

        //根据流程定义获取deployment
        String deploymentId = processDefinition.getDeploymentId();
        Deployment deployment = repositoryService.createDeploymentQuery()
                .deploymentId(deploymentId).singleResult();
        if (ObjectUtil.isEmpty(deployment)) {
            throw new MarsException(FlwDefinitionExceptionEnum.DEPLOYMENT_NOT_EXIST);
        }

        //获取资源名称
        String resourceName = processDefinition.getResourceName();
        List<String> resourceList = repositoryService.getDeploymentResourceNames(deploymentId);

        //遍历资源名称集合，匹配到则导出
        if (resourceList.contains(resourceName)) {
            try {
                final InputStream resourceStream = repositoryService.getResourceAsStream(deploymentId, resourceName);
                byte[] bytes = IoUtil.readBytes(resourceStream);
                response.reset();
                response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(resourceName, "UTF-8") + "\"");
                response.addHeader("Content-Length", "" + bytes.length);
                response.setContentType("application/octet-stream;charset=UTF-8");
                IoUtil.write(response.getOutputStream(), true, bytes);
            } catch (Exception e) {
                log.error(">>> 流程导出失败，流读取错误：{}", e.getMessage());
                throw new MarsException(FlwDefinitionExceptionEnum.RESOURCE_EXPORT_ERROR);
            }
        } else {
            log.error(">>> 流程导出失败，无该资源文件名称");
            throw new MarsException(FlwDefinitionExceptionEnum.RESOURCE_EXPORT_ERROR);
        }
    }

    @Override
    public void activeOrSuspend(FlwDefinitionDTO flwDefinitionDTO, boolean isSuspend) {
        //获取流程定义
        ProcessDefinition processDefinition = getProcessDefinition(flwDefinitionDTO);

        boolean suspended = processDefinition.isSuspended();

        //如果要挂起且已经是挂起状态
        if (suspended && isSuspend) {
            throw new MarsException(FlwDefinitionExceptionEnum.DEFINITION_SUSPEND);
        }
        if (isSuspend) {
            //挂起
            repositoryService.suspendProcessDefinitionById(flwDefinitionDTO.getId());
            //修改对应的入口状态
            flwShortcutService.changeStatus(flwDefinitionDTO.getId(), StatusType.DISABLE.getCode());
        } else {
            //激活
            repositoryService.activateProcessDefinitionById(flwDefinitionDTO.getId());
            //修改对应的入口状态
            flwShortcutService.changeStatus(flwDefinitionDTO.getId(), StatusType.ENABLE.getCode());
        }
    }

    @Override
    public void mapping(FlwDefinitionDTO flwDefinitionDTO) {
        //获取流程定义
        ProcessDefinition processDefinition = getProcessDefinition(flwDefinitionDTO);

        //根据流程定义获取deployment
        String deploymentId = processDefinition.getDeploymentId();
        Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult();
        if (ObjectUtil.isEmpty(deployment)) {
            throw new MarsException(FlwDefinitionExceptionEnum.DEPLOYMENT_NOT_EXIST);
        }

        //获取bpmnModel并转为modelNode
        BpmnModel bpmnModel = repositoryService.getBpmnModel(flwDefinitionDTO.getId());

        ObjectNode modelNode = new BpmnJsonConverter().convertToJson(bpmnModel);

        //构造模型
        ModelRepresentation model = new ModelRepresentation();
        model.setKey(processDefinition.getKey());
        model.setName(processDefinition.getName());
        model.setDescription(processDefinition.getDescription());
        model.setModelType(AbstractModel.MODEL_TYPE_BPMN);

        //创建模型
        modelService.createModel(model, modelNode.toString(), SecurityUtil.getUsername(request).getAccount());
    }

    @Override
    public List<FlwUserTaskVO> userTask(FlwDefinitionDTO flwDefinitionDTO) {
        //获取流程定义
        ProcessDefinition processDefinition = getProcessDefinition(flwDefinitionDTO);

        List<FlwUserTaskVO> flwUserTaskList = CollectionUtil.newArrayList();

        //根据流程定义获取deployment
        String deploymentId = processDefinition.getDeploymentId();
        Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult();
        if (ObjectUtil.isEmpty(deployment)) {
            throw new MarsException(FlwDefinitionExceptionEnum.DEPLOYMENT_NOT_EXIST);
        }

        //获取bpmnModel并转为modelNode
        BpmnModel bpmnModel = repositoryService.getBpmnModel(flwDefinitionDTO.getId());
        //获取主流程
        Process mainProcess = bpmnModel.getMainProcess();
        //获取用户任务节点类型，深入子流程
        mainProcess.findFlowElementsOfType(UserTask.class, true).forEach(userTask -> {
            FlwUserTaskVO flwUserTaskVO = $.copy(userTask, FlwUserTaskVO.class);
            flwUserTaskVO.setProcessDefinitionId(userTask.getId());
            flwUserTaskList.add(flwUserTaskVO);
        });
        return flwUserTaskList;
    }

    @Override
    public void delete(FlwDefinitionDTO flwDefinitionDTO) {
        //根据id获取流程定义
        String id = flwDefinitionDTO.getId();
        //获取流程定义
        ProcessDefinition processDefinition = this.queryProcessDefinition(id);
        String deploymentId = processDefinition.getDeploymentId();
        repositoryService.deleteDeployment(deploymentId, true);
        //删除该定义对应的表单配置信息
        flwFormService.delete(id);
        //同时删除该流程定义的选项配置
        flwOptionService.delete(id);
        //同时删除该流程定义的流程事件
        flwEventService.delete(id);
        //同时删除该流程定义的流程按钮
        flwButtonService.delete(id);
        //同时删除该流程定义对应的申请入口
        flwShortcutService.delete(id);
    }

    @Override
    public ProcessDefinition queryProcessDefinitionWithValidStatus(String processDefinitionId) {
        ProcessDefinition processDefinition = this.queryProcessDefinition(processDefinitionId);
        boolean suspended = processDefinition.isSuspended();
        if (suspended) {
            throw new MarsException(FlwDefinitionExceptionEnum.DEFINITION_SUSPEND);
        }
        return processDefinition;
    }

    @Override
    public String getMultiInstanceActAssigneeParam(String processDefinitionId, String currentActId) {
        AtomicReference<String> resultParam = new AtomicReference<>();
        ProcessDefinition processDefinition = this.queryProcessDefinition(processDefinitionId);
        //获取bpmnModel并转为modelNode
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        //获取主流程
        Process mainProcess = bpmnModel.getMainProcess();
        //获取用户任务节点类型，深入子流程
        mainProcess.findFlowElementsOfType(UserTask.class, true).forEach(userTask -> {
            String userTaskId = userTask.getId();
            if (userTaskId.equals(currentActId)) {
                Object behavior = userTask.getBehavior();
                if (ObjectUtil.isNotNull(behavior)) {
                    //并行多实例节点
                    if (behavior instanceof ParallelMultiInstanceBehavior) {
                        ParallelMultiInstanceBehavior parallelMultiInstanceBehavior =
                                (ParallelMultiInstanceBehavior) behavior;
                        String collectionElementVariable = parallelMultiInstanceBehavior
                                .getCollectionElementVariable();
                        if (ObjectUtil.isNotEmpty(collectionElementVariable)) {
                            resultParam.set(collectionElementVariable);
                        }
                    }
                    //串行多实例节点
                    if (behavior instanceof SequentialMultiInstanceBehavior) {
                        SequentialMultiInstanceBehavior sequentialMultiInstanceBehavior =
                                (SequentialMultiInstanceBehavior) behavior;
                        String collectionElementVariable = sequentialMultiInstanceBehavior
                                .getCollectionElementVariable();
                        if (ObjectUtil.isNotEmpty(collectionElementVariable)) {
                            resultParam.set(collectionElementVariable);
                        }
                    }
                }
            }
        });
        return resultParam.get();
    }

    /**
     * 根据ID获取流程定义
     *
     * @param flwDefinitionDTO
     * @return
     */
    private ProcessDefinition getProcessDefinition(FlwDefinitionDTO flwDefinitionDTO) {
        //根据id获取流程定义
        String id = flwDefinitionDTO.getId();
        //获取流程定义
        ProcessDefinition processDefinition = this.queryProcessDefinition(id);
        return processDefinition;
    }
}
